#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include "gd32f30x.h"
#include "delay.h"
#include "systick.h"
#include "RTT_Debug.h"

#define GET_I2C_SDA()             gpio_input_bit_get(GPIOA, GPIO_PIN_4)    // 读取SDA端口
#define SET_I2C_SCL()             gpio_bit_set(GPIOA, GPIO_PIN_5)          // 时钟线SCL输出高电平
#define CLR_I2C_SCL()             gpio_bit_reset(GPIOA, GPIO_PIN_5)        // 时钟线SCL输出低电平
#define SET_I2C_SDA()             gpio_bit_set(GPIOA, GPIO_PIN_4)          // 数据线SDA输出高电平
#define CLR_I2C_SDA()             gpio_bit_reset(GPIOA, GPIO_PIN_4)        // 数据线SDA输出低电平

static void GpioInit(void)
{
	rcu_periph_clock_enable(RCU_GPIOA);
    gpio_init(GPIOA, GPIO_MODE_OUT_OD, GPIO_OSPEED_10MHZ, GPIO_PIN_4 | GPIO_PIN_5);
}

/**
*******************************************************************
* @function 产生IIC起始时序，准备发送或接收数据前必须由起始序列开始 
* @param
* @return 
* @brief    SCL为高电平时，SDA由高电平向低电平跳变，开始传输数据 
*           生成下图所示的波形图，即为起始时序 
*                1 2    3     4   
*                    __________     
*           SCL : __/          \_____ 
*                 ________          
*           SDA :         \___________ 
*******************************************************************
*/
static void I2CStart(void)
{
	SET_I2C_SDA();          // 1#数据线SDA输出高电平
	SET_I2C_SCL();          // 2#时钟线SCL输出高电平   
	DelayNus(4);            // 延时4us
	CLR_I2C_SDA();          // 3#数据线SDA输出低电平 
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平，保持I2C的时钟线SCL为低电平，准备发送或接收数据 
	DelayNus(4);            // 延时4us
}

/**
*******************************************************************
* @function 产生IIC停止时序  
* @param
* @return 
* @brief    SCL为高电平时，SDA由低电平向高电平跳变，结束传输数据 
*          生成下图所示的波形图，即为停止时序 
*                1 2   3  4   
*                       _______________     
*          SCL : ______/          
*                __        ____________  
*          SDA:    \______/
*******************************************************************
*/
static void I2CStop(void)
{
	CLR_I2C_SDA();          //2#数据线SDA输出低电平
	DelayNus(4);            //延时4us
	SET_I2C_SCL();          //3#时钟线SCL输出高电平
	DelayNus(4);  
	SET_I2C_SDA();          //4#数据线SDA输出高电平，发送I2C总线结束信号
}

/**
*******************************************************************
* @function 发送一字节，数据从高位开始发送出去
* @param    byte
* @return 
* @brief    下面是具体的时序图 
*                1 2     3      4
*                         ______
*           SCL: ________/      \______    
*                ______________________    
*           SDA: \\\___________________
*******************************************************************
*/
static void I2CSendByte(uint8_t byte)
{                          
	for (uint8_t i = 0; i < 8; i++)   // 循环8次，从高到低取出字节的8个位
	{     
		if ((byte & 0x80))            // 2#取出字节最高位，并判断为‘0’还是‘1’，从而做出相应的操作
		{
			SET_I2C_SDA();            // 数据线SDA输出高电平，数据位为‘1’
		}
		else
		{  
			CLR_I2C_SDA();      	  // 数据线SDA输出低电平，数据位为‘0’
		}
		
		byte <<= 1;            		  // 左移一位，次高位移到最高位
		
		DelayNus(4);          		  // 延时4us
		SET_I2C_SCL();                // 3#时钟线SCL输出高电平
		DelayNus(4);          		  // 延时4us
		CLR_I2C_SCL();        		  // 4#时钟线SCL输出低电平
		DelayNus(4);                  // 延时4us  
	}  
}

/**
*******************************************************************
* @function 读取一字节数据
* @param    
* @return   读取的字节
* @brief    下面是具体的时序图
*                       ______
*           SCL: ______/      \___        
*                ____________________    
*           SDA: \\\\______________\\\
*******************************************************************
*/
static uint8_t I2CReadByte(void)
{
	uint8_t byte = 0;           		// byte用来存放接收的数据
	SET_I2C_SDA();                      // 释放SDA
	for (uint8_t i = 0; i < 8; i++)     // 循环8次，从高到低读取字节的8个位
	{
		SET_I2C_SCL();          		// 时钟线SCL输出高电平
		DelayNus(4);            		// 延时4us
		byte <<= 1;          			// 左移一位，空出新的最低位

		if (GET_I2C_SDA())       		// 读取数据线SDA的数据位
		{
			byte++;            			// 在SCL的上升沿后，数据已经稳定，因此可以取该数据，存入最低位
		}
		CLR_I2C_SCL();          		// 时钟线SCL输出低电平
		DelayNus(4);            		// 延时4us
	} 

	return byte;           				// 返回读取到的数据
}

/**
*******************************************************************
* @function 等待接收端的应答信号
* @param    
* @return   1，接收应答失败；0，接收应答成功
* @brief    当SDA拉低后，表示接收到ACK信号，然后，拉低SCL，
*           此处表示发送端收到接收端的ACK
*                _______|____     
*           SCL:        |    \_________    
*                _______|     
*           SDA:         \_____________ 
*******************************************************************
*/
static bool I2CWaitAck(void)
{
	uint16_t errTimes = 0;
	
	SET_I2C_SDA();             // 释放SDA总线,很重要
	DelayNus(4);               // 延时4us
	
	SET_I2C_SCL();             // 时钟线SCL输出高电平
	DelayNus(4);               // 延时4us

	while (GET_I2C_SDA())      // 读回来的数据如果是高电平，即接收端没有应答
	{
		errTimes++;            // 计数器加1

		if (errTimes > 250)    // 如果超过250次，则判断为接收端出现故障，因此发送结束信号
		{
			I2CStop();         // 产生一个停止信号
			return false;      // 返回值为1，表示没有收到应答信号
		}
	}

	CLR_I2C_SCL();             // 表示已收到应答信号，时钟线SCL输出低电平
	DelayNus(4);               // 延时4us
	
	return true;               // 返回值为0，表示接收应答成功  
}

/**
*******************************************************************
* @function 发送应答信号
* @param    
* @return   
* @brief    下面是具体的时序图 
*                 1 2     3      4      5     
*                         ______
*           SCL: ________/      \____________    
*                __                     ______
*           SDA:   \___________________/        
*******************************************************************
*/
void I2CSendAck(void)
{
	CLR_I2C_SDA();          // 2#数据线SDA输出低电平
	DelayNus(4);            // 延时4us
	SET_I2C_SCL();          // 3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉低，为应答信号
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平
	DelayNus(4);            // 延时4us
	SET_I2C_SDA();          // 5#数据线SDA输出高电平，释放SDA总线,很重要
}

/**
*******************************************************************
* @function 发送非应答信号
* @param    
* @return   
* @brief    下面是具体的时序图 
*               1 2     3      4
*                        ______
*          SCL: ________/      \______    
*               __ ___________________    
*          SDA: __/
*******************************************************************
*/
void I2CSendNack(void)
{
	SET_I2C_SDA();          // 2#数据线SDA输出高电平
	DelayNus(4);            // 延时4us
	SET_I2C_SCL();          // 3#时钟线SCL输出高电平，在SCL上升沿前就要把SDA拉高，为非应答信号
	DelayNus(4);            // 延时4us
	CLR_I2C_SCL();          // 4#时钟线SCL输出低电平
	DelayNus(4);            // 延时4us
}

#define SHT20_DEV_ADDR			        0x80	// SHT20的设备地址
#define SHT20_I2C_WR			        0		// 写控制bit
#define SHT20_I2C_RD	                1		// 读控制bit
#define TRIG_TEMP_MEASUREMENT_HM    	0xE3    // command trig. temp meas. hold master
#define TRIG_HUMI_MEASUREMENT_HM		0xE5    // command trig. humidity meas. hold master
#define TRIG_TEMP_MEASUREMENT_POLL		0xF3    // command trig. temp meas. no hold master
#define TRIG_HUMI_MEASUREMENT_POLL	    0xF5    // command trig. humidity meas. no hold master
#define SHT20_SOFT_RESET                0xFE    // command soft reset
#define SHT20_RESOLUTION_REG    		0xE6    // 设置分辨率寄存器地址
#define SHT20_RESOLUTION_VAL    		0x83    // 设置分辨率bit7 = 1,bit0 = 0,对应湿度10bit，温度13bit
#define SHT20_READ_REG  				0XE7

static void Sht20SoftReset(void)                    
{
	I2CStart();
	I2CSendByte(SHT20_DEV_ADDR | SHT20_I2C_WR);
	if (!I2CWaitAck())
	{
		goto i2c_err;
	}
	I2CSendByte(SHT20_SOFT_RESET);
	if (!I2CWaitAck())
	{
		goto i2c_err;
	}
	I2CStop();
	return;

i2c_err:          // 命令执行失败后，要发送停止信号，避免影响I2C总线上其他设备
	DBG_Warning("sht20 sensor reset failed!\n");
	I2CStop();
}
/*
static bool Sht20SetResolution(void)
{
	I2CStart();
	I2CSendByte(SHT20_DEV_ADDR | SHT20_I2C_WR);
	if (!I2CWaitAck())
	{
		goto i2c_err;
	}
	I2CSendByte(SHT20_RESOLUTION_REG);
	if (!I2CWaitAck())
	{
		goto i2c_err;
	}
	I2CSendByte(SHT20_RESOLUTION_VAL);
	if (!I2CWaitAck())
	{
		goto i2c_err;
	}	
	
	I2CStop();
	return true;

i2c_err:          // 命令执行失败后，要发送停止信号，避免影响I2C总线上其他设备
	I2CStop();
	return false;
}
*/
#define  POLYNOMIAL  0x131     // P(x)=x^8+x^5+x^4+1 = 100110001s

static bool Sht20CheckCrc(uint8_t *data, uint8_t nbrOfBytes, uint8_t checkSum)
{
	uint8_t crc = 0;	
	uint8_t byteCtr;
	uint8_t bit;

	for (byteCtr = 0; byteCtr < nbrOfBytes; ++byteCtr)
	{ 
		crc ^= (data[byteCtr]);
		for (bit = 8; bit > 0; --bit)
		{ 
			if (crc & 0x80) 
			{	
				crc = (crc << 1) ^ POLYNOMIAL;
			}
			else
			{
				crc = (crc << 1);
			}
		}
	}
	if (crc != checkSum)
	{
		return false;
	}
	return true;
}

static float g_humiData;
static float g_tempData;
static int8_t g_tempCoeff;   // 温度校准系数

/*
 饱和绝对湿度值g/m3，相对湿度(100%RH)，放大10倍
*/
static const uint16_t g_satHumiTab[] =
{
	49, 52, 56, 60, 64, 68, 73, 78, 83, 88,	                 // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9		        ℃
	94,100,107,114,121,129,137,145,154,163,                  // 10, 11, 12, 13, 14, 15, 16, 17, 18, 19		℃	
	173,184,195,206,218,231,244,258,273,288,                 // 20, 21, 22, 23, 24, 25, 26, 27, 28, 29		℃		
	304,321,339,357,376,397,418,440,463,487,                 // 30, 31, 32, 33, 34, 35, 36, 37, 38, 39		℃
	512,538,565,594,624,655,687,720,755,792,                 // 40, 41, 42, 43, 44, 45, 46, 47, 48, 49		℃
	829,869,910,952,996,1042,1090,1139,1190,1244,            // 50, 51, 52, 53, 54, 55, 56, 57, 58, 59		℃
	1299,1356,1415,1476,1540,1606,1674,1745,1818,1893,       // 60, 61, 62, 63, 64, 65, 66, 67, 68, 69		℃
    1971,2052,2136,2222,2311,2403,2498,2596,2698,2802,       // 70, 71, 72, 73, 74, 75, 76, 77, 78, 79		℃
    2910,3021,3136,3254,3375,3501,3630,3763,3900,4041,       // 80, 81, 82, 83, 84, 85, 86, 87, 88, 89		℃
    4186,4336,4489,4647,4810,4977,5149,5325,5506,5693,       // 90, 91, 92, 93, 94, 95, 96, 97, 98, 99		℃			
};

static float CaliHumiWithTemp(float rawHumi, float rawTemp, float caliTemp)
{
	uint16_t rawTempIndex = (uint16_t)(rawTemp + 0.5f);
	uint16_t caliTempIndex = (uint16_t)(caliTemp + 0.5f);
	float caliHumi =  g_satHumiTab[rawTempIndex] * rawHumi / g_satHumiTab[caliTempIndex];
//	DBG_log("rawTemp: %d, rawHumi: %d\n", (uint8_t)rawTemp, (uint8_t)rawHumi); 
//	DBG_log("caliTemp: %d, caliHumi: %d\n", (uint8_t)caliTemp, (uint8_t)caliHumi);
	if (caliHumi > 99.0f)
	{
		return 99.0f;
	}
	return caliHumi;
}

static float CaliTemp(float temp)
{
	temp += g_tempCoeff;
	if (temp < 0.0f)
	{
		return 0.0f;
	}
	if (temp > 99.0f)
	{
		return 99.0f;
	}
	return temp;
}

typedef enum
{
	MEASURE_TEMP_START,
	MEASURE_TEMP_WAIT,
	MEASURE_TEMP_FINISH,
	MEASURE_HUMI_START,
	MEASURE_HUMI_WAIT,
	MEASURE_HUMI_FINISH,
} MeasureState_t;
 
#define MEASURE_TIMEOUT_MS  500  // TempHumiDrvProc被调度周期时间不能大于这个数值，否则会判断为超时

void TempHumiDrvProc(void)
{
	static MeasureState_t s_state = MEASURE_TEMP_START;
	static uint64_t s_measureTempStartTime;
	uint8_t crcData;
	uint8_t orgiBuf[2];
	uint16_t origData;
	static float s_rawTempData, s_rawHumiData;
	switch (s_state)
	{
		case MEASURE_TEMP_START:
			I2CStart();
			I2CSendByte(SHT20_DEV_ADDR | SHT20_I2C_WR);
			if (!I2CWaitAck())
			{
				goto i2c_err;
			}
			I2CSendByte(TRIG_TEMP_MEASUREMENT_POLL);
			if (!I2CWaitAck())
			{
				goto i2c_err;
			}
			s_measureTempStartTime = GetSysRunTime();
			s_state = MEASURE_TEMP_WAIT;
			break;
		case MEASURE_TEMP_WAIT:
			if (GetSysRunTime() - s_measureTempStartTime > MEASURE_TIMEOUT_MS)
			{
				s_state = MEASURE_TEMP_START;
				goto i2c_err;
			}
				
			I2CStart();
			I2CSendByte(SHT20_DEV_ADDR | SHT20_I2C_RD);
			if (I2CWaitAck())
			{
				s_state = MEASURE_TEMP_FINISH;
			}
			break;
		case MEASURE_TEMP_FINISH:
			orgiBuf[0] = I2CReadByte();
			I2CSendAck();
			orgiBuf[1] = I2CReadByte();
			I2CSendAck();
			crcData = I2CReadByte();
			I2CSendNack();
			I2CStop();
			if (Sht20CheckCrc(orgiBuf, 2, crcData))
			{
				origData = ((uint16_t)orgiBuf[0] << 8) + orgiBuf[1];
				origData &= 0xFFFC;           // clear bits [1..0] (status bits)
				s_rawTempData = -46.85f + 175.72f / 65536 * (float)origData; // T= -46.85 + 175.72 * ST/2^16
				g_tempData = CaliTemp(s_rawTempData);
				s_state = MEASURE_HUMI_START;
				break;	
			}
			s_state = MEASURE_TEMP_START;
			break;
		
		case MEASURE_HUMI_START:
			I2CStart();
			I2CSendByte(SHT20_DEV_ADDR | SHT20_I2C_WR);
			if (!I2CWaitAck())
			{
				goto i2c_err;
			}
			I2CSendByte(TRIG_HUMI_MEASUREMENT_POLL);
			if (!I2CWaitAck())
			{
				goto i2c_err;
			}
			s_measureTempStartTime = GetSysRunTime();
			s_state = MEASURE_HUMI_WAIT;
			break;
		case MEASURE_HUMI_WAIT:
			if (GetSysRunTime() - s_measureTempStartTime > MEASURE_TIMEOUT_MS)
			{
				s_state = MEASURE_HUMI_START;
				goto i2c_err;
			}
				
			I2CStart();
			I2CSendByte(SHT20_DEV_ADDR | SHT20_I2C_RD);
			if (I2CWaitAck())
			{
				s_state = MEASURE_HUMI_FINISH;
			}
			break;
		case MEASURE_HUMI_FINISH:
			orgiBuf[0] = I2CReadByte();
			I2CSendAck();
			orgiBuf[1] = I2CReadByte();
			I2CSendAck();
			crcData = I2CReadByte();
			I2CSendNack();
			I2CStop();
			if (Sht20CheckCrc(orgiBuf, 2, crcData))
			{
				origData = ((uint16_t)orgiBuf[0] << 8) + orgiBuf[1];
				origData &= 0xFFFC;           // clear bits [1..0] (status bits)
				s_rawHumiData = -6.0f + 125.0f / 65536 * (float)origData; // RH = -6 + 125 * SRH/2^16
				g_humiData = CaliHumiWithTemp(s_rawHumiData, s_rawTempData, g_tempData);
				s_state = MEASURE_TEMP_START;
				break;	
			}
			s_state = MEASURE_HUMI_START;
			break;
	}
	return;
i2c_err:          // 命令执行失败后，要发送停止信号，避免影响I2C总线上其他设备
	DBG_Warning("sht20 sensor is working failed!\n");
	I2CStop();
	return;
}


float GetTempData(void)
{
	return g_tempData;
}

uint8_t GetHumiData(void)
{
	return (uint8_t)(g_humiData + 0.5f);
}

void TempHumiDrvInit(void)
{
	GpioInit();
	Sht20SoftReset();
	g_tempCoeff = -2;
}

void TempHumiDrvTest(void)
{
	DBG_log("temp: %d, humi: %d\n", (uint8_t)(GetTempData()), GetHumiData()); 
}
